/*
 * Copyright (C) 2019 Peng fei Pan <panpfpanpf@outlook.me>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.panpf.sketch.shaper;

import me.panpf.sketch.ImageView;
import me.panpf.sketch.decode.ResizeCalculator;
import me.panpf.sketch.request.ShapeSize;
import me.panpf.sketch.util.Utils;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;

/**
 * 圆角矩形的绘制时图片整形器，还可以有描边
 */
@SuppressWarnings({"WeakerAccess"})
public class RoundRectImageShaper implements ImageShaper {
    private float[] outerRadii;
    private Rect boundsCached = new Rect();

    private Path bitmapPath = new Path();
    private Path innerStrokePath;
    private Path outerStrokePath;
    private Path clipPath;

    private int strokeWidth;
    private int strokeColor;

    private Paint strokePaint;

    private Rect boundsBack;
    private RectFloat rectF;
    private Path path;

    private Context context;
    private int resID = -1;
    private String rawPath = null;
    private boolean correctImageOrientationDisabled = false;
    private ResizeCalculator resizeCalculator;
    private RectFloat rectSrc; //源区域

    public RoundRectImageShaper setContext(Context context) {
        this.context = context;
        return this;
    }

    public RoundRectImageShaper setResID(int resID) {
        this.resID = resID;
        return this;
    }

    public RoundRectImageShaper setRawPath(String rawPath) {
        this.rawPath = rawPath;
        return this;
    }

    public void setCorrectImageOrientationDisabled(boolean correctImageOrientationDisabled) {
        this.correctImageOrientationDisabled = correctImageOrientationDisabled;
        rectSrc = new RectFloat(0, 0, 0, 0);
        resizeCalculator = new ResizeCalculator();
    }

    public RoundRectImageShaper setRadiis(float radii) {
        this.outerRadii = new float[]{radii, radii, radii, radii,
                radii, radii, radii, radii};
        return this;
    }

    public RoundRectImageShaper(float[] radiis) {
        if (radiis == null || radiis.length < 8) {
            throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
        }
        this.outerRadii = radiis;
    }

    public RoundRectImageShaper(float topLeftRadii, float topRightRadii, float bottomLeftRadii, float bottomRightRadii) {
        this(new float[]{topLeftRadii, topLeftRadii, topRightRadii, topRightRadii,
                bottomLeftRadii, bottomLeftRadii, bottomRightRadii, bottomRightRadii});
    }

    public RoundRectImageShaper(float radii) {
        this(radii, radii, radii, radii);
    }

    public float[] getOuterRadii() {
        if (outerRadii != null) {
            float[] temp = new float[]{};
            System.arraycopy(outerRadii, 0, temp, 0, outerRadii.length);
            return temp;
        }
        return null;
    }

    public int getStrokeColor() {
        return strokeColor;
    }

    public RoundRectImageShaper setStroke(int strokeColor, int strokeWidth) {
        this.strokeColor = strokeColor;
        this.strokeWidth = strokeWidth;
        updatePaint();
        return this;
    }

    public int getStrokeWidth() {
        return strokeWidth;
    }

    private void updatePaint() {
        if (hasStroke()) {
            if (strokePaint == null) {
                strokePaint = new Paint();
                strokePaint.setStyle(Paint.Style.STROKE_STYLE);
                strokePaint.setAntiAlias(true);
            }

            strokePaint.setColor(new Color(strokeColor));
            strokePaint.setStrokeWidth(strokeWidth);

            if (innerStrokePath == null) {
                innerStrokePath = new Path();
            }

            if (outerStrokePath == null) {
                outerStrokePath = new Path();
            }

            if (clipPath == null) {
                clipPath = new Path();
            }
        }
    }

    private boolean hasStroke() {
        return strokeColor != 0 && strokeWidth > 0;
    }

    @Override
    public Path getPath(Rect bounds) {
        if (path != null && boundsBack != null && boundsBack.equals(bounds)) {
            return path;
        }

        if (boundsBack == null) {
            boundsBack = new Rect();
        }
        boundsBack.set(bounds.left, bounds.top, bounds.right, bounds.bottom);

        if (path == null) {
            path = new Path();
        }
        path.reset();

        if (rectF == null) {
            rectF = new RectFloat();
        }
        rectF.modify(boundsBack);

        path.addRoundRect(rectF, outerRadii, Path.Direction.CLOCK_WISE);

        return path;
    }

    @Override
    public void onUpdateShaderMatrix(Matrix matrix, Rect bounds, int bitmapWidth, int bitmapHeight,
                                     ShapeSize shapeSize, Rect srcRect) {

    }

    @Override
    public void draw(Canvas canvas, Paint paint, Rect bounds) {
        int pixelWidth = bounds.right;
        int pixleHeight = bounds.bottom;
        PixelMap pixelMap = rawPath == null ? Utils.getPixelMap(context, resID) : Utils.getPixelMap(context, rawPath);

        if (pixelMap != null && correctImageOrientationDisabled) {
            int width = Utils.getDisplayWidthInPx(context);
            bounds.left = (int) ((double) bounds.left + (width - (double) bounds.right) / 2d);
            bounds.right = (int) ((double) bounds.right + ((double) width - bounds.right) / 2d);
        }
        RectFloat rectF = new RectFloat(bounds);
        if (!boundsCached.equals(bounds)) {

            bitmapPath.reset();
            bitmapPath.addRoundRect(rectF, outerRadii, Path.Direction.CLOCK_WISE);

            // 假如描边宽度是10，那么会是5个像素在图片外面，5个像素在图片里面
            // 因为描边会有一半是在图片外面，所以如果图片被紧紧（没有缝隙）包括在Layout中，那么描边就会丢失一半
            if (hasStroke()) {
                // 内圈，往图片里面偏移描边宽度的一半，让描边都在图片里面，都在里面导致圆角部分会露出来一些
                final float offset = (float) (strokeWidth / 2d);
                rectF.modify((float) (bounds.left + (double) offset), (float) (bounds.top + (double) offset),
                        (float) (bounds.right - (double) offset), (float) (bounds.bottom - (double) offset));
                innerStrokePath.reset();
                innerStrokePath.addRoundRect(rectF, outerRadii, Path.Direction.CLOCK_WISE);

                // 外圈，主要用来盖住内圈描边无法覆盖导致露出的圆角部分
                outerStrokePath.reset();
                rectF.modify(bounds.left, bounds.top, bounds.right, bounds.bottom);
                outerStrokePath.addRoundRect(rectF, outerRadii, Path.Direction.CLOCK_WISE);

                rectF.modify(bounds);
                clipPath.addRoundRect(rectF, outerRadii, Path.Direction.CLOCK_WISE);
            }
        }

        paint.setAntiAlias(true);
        if (pixelMap == null) {
            canvas.drawPath(bitmapPath, paint);
        } else {
            if (correctImageOrientationDisabled) {
                ResizeCalculator.Mapping mapping = resizeCalculator.calculator(pixelMap.getImageInfo().size.width,
                        pixelMap.getImageInfo().size.height, pixelWidth, pixleHeight, ImageView.ScaleType.CLIP_CENTER, true);
                rectSrc.left = mapping.srcRect.left;
                rectSrc.top = mapping.srcRect.top;
                rectSrc.right = mapping.srcRect.right;
                rectSrc.bottom = mapping.srcRect.bottom;
                canvas.drawPixelMapHolderRect(new PixelMapHolder(pixelMap), rectSrc, rectF, new Paint());
            } else {
                Paint pixelMapPaint = new Paint();
                PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
                options.size = new Size(pixelWidth, pixleHeight);
                PixelMapHolder pixelMapHolder = new PixelMapHolder(PixelMap.create(pixelMap, options));
                PixelMapShader shader = new PixelMapShader(pixelMapHolder, Shader.TileMode.CLAMP_TILEMODE, Shader.TileMode.CLAMP_TILEMODE);
                pixelMapPaint.setShader(shader, Paint.ShaderType.PIXELMAP_SHADER);
                canvas.drawRoundRect(rectF, this.outerRadii[0], this.outerRadii[0], pixelMapPaint);
            }
        }

        if (hasStroke() && strokePaint != null) {
            if (pixelMap != null) {
                canvas.clipPath(bitmapPath, Canvas.ClipOp.INTERSECT);
                bitmapPath.reset();
                bitmapPath.addRoundRect(rectF, outerRadii, Path.Direction.COUNTER_CLOCK_WISE);
            } else {
                // 裁掉外圈跑出图片的部分
                canvas.clipPath(clipPath, Canvas.ClipOp.DIFFERENCE);
            }

            canvas.drawPath(innerStrokePath, strokePaint);
            canvas.drawPath(outerStrokePath, strokePaint);
        }
    }

}
